package ai.people.platform.graph.service.impl;

import ai.people.core.nebula.util.NebulaQueryUtils;
import ai.people.core.nebula.util.VidGenerateUtils;
import ai.people.netmon.framework.constant.GraphDirection;
import ai.people.netmon.framework.domain.graph.enums.GraphTagEnum;
import ai.people.netmon.framework.domain.graph.request.InsertRelationBatch;
import ai.people.netmon.framework.domain.graph.request.InsertRelationRequest;
import ai.people.netmon.framework.domain.graph.request.InsertRequest;
import ai.people.netmon.framework.domain.graph.request.InsertRequestBatch;
import ai.people.netmon.framework.exception.enums.CommonEnum;
import ai.people.netmon.framework.exception.enums.GraphExceptionEnum;
import ai.people.netmon.framework.exception.type.BusinessException;
import ai.people.netmon.framework.utils.AssertUtils;
import ai.people.platform.graph.service.GraphDataService;
import ai.people.platform.graph.service.StatisticsService;
import com.alibaba.fastjson.JSONObject;
import com.people.core.validator.util.ValidatorUtils;
import com.vesoft.nebula.client.graph.exception.ClientServerIncompatibleException;
import com.vesoft.nebula.client.graph.exception.IOErrorException;
import com.vesoft.nebula.client.graph.net.SessionWrapper;
import com.vesoft.nebula.client.graph.net.SessionsManager;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;

import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * @author zhenglin
 * @date 2022/5/13
 */
@Service
@Slf4j
@RequiredArgsConstructor
public class GraphDataServiceImpl implements GraphDataService {

    private final SessionsManager sessionsManager;

    private final StatisticsService statisticsService;
    @Data
    @NoArgsConstructor
    private static class Result{
        private String vid;
        private String gql;

        public Result(String vid, String insertVertexQuery) {
            this.vid = vid;
            this.gql = insertVertexQuery;
        }
    }

    /**
     * 插入数据
     *
     * @param request 请求
     */
    @Override
    public String insert(InsertRequest request) throws Exception {
        SessionWrapper session = sessionsManager.getSessionWrapper();
        try {
            String vid = check2Handle(request, session, true).getVid();
            statisticsService.put(vid);
            return vid;
        } finally {
            sessionsManager.returnSessionWrapper(session);
        }

    }

    /**
     * 检查格式 并保存数据
     *
     * @param request 请求
     * @param session 会话
     * @param save    保存
     * @return {@link String}
     * @throws IOErrorException ioerror异常那么
     */
    private Result check2Handle(InsertRequest request, SessionWrapper session, boolean save) throws IOErrorException {
        String tagName = request.getTagName();
        GraphTagEnum tagEnum = GraphTagEnum.getByTagName(tagName);
        //如果是档案类型，在此接口不支持
        if (tagEnum == GraphTagEnum.PERSON_ARCHIVES || tagEnum == null) {
            throw new BusinessException(GraphExceptionEnum.TAG_NOT_SUPPORT);
        }
        Map<String, Object> tagProperties = request.getTagProperties();
        Object data = JSONObject.parseObject(JSONObject.toJSONString(tagProperties), tagEnum.clazz);
        //校验
        ValidatorUtils.validator(data);
        //构建vid
        List<Object> vidValues = VidGenerateUtils.handleSpecialVid(tagProperties, tagEnum, null);
        String vid = NebulaQueryUtils.buildVid(tagEnum.vidPrefix, vidValues);

        String insertVertexQuery = NebulaQueryUtils.buildInsertVertexQuery(tagName, vid, data);

        if (save) {
            boolean b = NebulaQueryUtils.execute(session, insertVertexQuery);
            AssertUtils.isTrue(b, CommonEnum.FAIL);
        }
        return new Result(vid,insertVertexQuery);
    }

    /**
     * 插入数据(带有关系)
     *
     * @param request 请求
     */
    @Override
    public void insertWithEdge(InsertRelationRequest request) throws Exception {
        SessionWrapper session = sessionsManager.getSessionWrapper();
        try {
            InsertRequest node1 = request.getNode1();
            String node1Vid = insert(node1);
            statisticsService.put(node1Vid);
            InsertRequest node2 = request.getNode2();
            String node2Vid = insert(node2);
            statisticsService.put(node2Vid);

            String edge = request.getEdge();
            String direction = request.getDirection();
            boolean b = false;
            switch (direction) {
                case GraphDirection.FROM:
                    b = NebulaQueryUtils.insertEdge(session, node1Vid, node2Vid, edge, null);
                    break;
                case GraphDirection.TO:
                    b = NebulaQueryUtils.insertEdge(session, node2Vid, node1Vid, edge, null);
                    break;
                case GraphDirection.BOTH:
                    b = NebulaQueryUtils.insertEdge(session, node1Vid, node2Vid, edge, null);
                    b &= NebulaQueryUtils.insertEdge(session, node2Vid, node1Vid, edge, null);
                    break;
                default:
                    break;
            }
            AssertUtils.isTrue(b, CommonEnum.FAIL);
        } finally {
            sessionsManager.returnSessionWrapper(session);
        }
    }

    /**
     * 批量保存
     *
     * @param insertRequest 插入请求
     * @return boolean
     */
    @Override
    public boolean batchSave(InsertRequestBatch insertRequest) throws ClientServerIncompatibleException, IOErrorException, UnsupportedEncodingException {
        SessionWrapper session = sessionsManager.getSessionWrapper();
        try {
            List<InsertRequest> insertRequests = insertRequest.getRequests();
            List<String> ngls = new ArrayList<>();
            List<String> vids = new ArrayList<>();
            for (InsertRequest request : insertRequests) {
                Result result = check2Handle(request, session, false);
                ngls.add(result.getGql());
                vids.add(result.getVid());
            }
            if (CollectionUtils.isNotEmpty(ngls)) {
                boolean b = NebulaQueryUtils.executeBatch(session, ngls);
                AssertUtils.isTrue(b, CommonEnum.FAIL);
                statisticsService.put(vids);
                return true;
            }
            return false;
        } finally {
            sessionsManager.returnSessionWrapper(session);
        }
    }

    /**
     * 批量保存(带有关系)
     *
     * @param insertRequest 插入请求
     * @return boolean
     */
    @Override
    public boolean batchSaveWithEdge(InsertRelationBatch insertRequest) throws ClientServerIncompatibleException, IOErrorException, UnsupportedEncodingException {
        SessionWrapper session = sessionsManager.getSessionWrapper();
        try {
            List<String> gqls = new ArrayList<>();
            List<InsertRelationRequest> requests = insertRequest.getRequests();
            List<String> vids = new ArrayList<>();
            for (InsertRelationRequest request : requests) {
                InsertRequest node1 = request.getNode1();
                Result result1 = check2Handle(node1, session, false);
                String srcVid = result1.getVid();
                vids.add(srcVid);
                InsertRequest node2 = request.getNode2();
                Result result2 = check2Handle(node2, session, false);
                String dstVid = result2.getVid();
                vids.add(dstVid);

                gqls.add(result1.getGql());
                gqls.add(result2.getGql());

                String edge = request.getEdge();
                String direction = request.getDirection();
                String fromQuery = null;
                String toQuery = null;
                switch (direction) {
                    case GraphDirection.FROM:
                        fromQuery = NebulaQueryUtils.buildCreateEdgeQuery(srcVid, dstVid, edge, null);
                        break;
                    case GraphDirection.TO:
                        toQuery = NebulaQueryUtils.buildCreateEdgeQuery(dstVid, srcVid, edge, null);
                        break;
                    case GraphDirection.BOTH:
                        fromQuery = NebulaQueryUtils.buildCreateEdgeQuery(srcVid, dstVid, edge, null);
                        toQuery = NebulaQueryUtils.buildCreateEdgeQuery(dstVid, srcVid, edge, null);
                        break;
                    default:
                        break;
                }
                if (StringUtils.isNotBlank(fromQuery)) {
                    gqls.add(fromQuery);
                }
                if (StringUtils.isNotBlank(toQuery)) {
                    gqls.add(toQuery);
                }
            }
            if (CollectionUtils.isNotEmpty(gqls)) {
                boolean b = NebulaQueryUtils.executeBatch(session, gqls);
                AssertUtils.isTrue(b, CommonEnum.FAIL);
                statisticsService.put(vids);
                return true;
            }
            return false;
        } finally {
            sessionsManager.returnSessionWrapper(session);
        }
    }
}
